package com.gitee.kamismile.stone.registry.eureka;

import com.gitee.kamismile.stone.registry.api.IServiceDiscovery;
import com.gitee.kamismile.stone.rpc.common.properties.StoneRpcConfigProperties;
import com.gitee.kamismile.stone.rpc.common.provider.consumer.StoneConsumerMeta;
import com.gitee.kamismile.stone.rpc.common.provider.consumer.StoneConsumerVersion;
import com.gitee.kamismile.stone.rpc.common.support.StoneURL;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.netflix.appinfo.ApplicationInfoManager;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.CacheRefreshedEvent;
import com.netflix.discovery.EurekaClient;
import com.netflix.discovery.EurekaEvent;
import com.netflix.discovery.shared.Applications;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean;

import java.util.*;

public class EurekaServiceDiscovery implements IServiceDiscovery {

    @Autowired
    private EurekaClient eurekaClient;

    @Autowired
    private ApplicationInfoManager applicationInfoManager;

    @Autowired
    private StoneRpcConfigProperties stoneRpcConfigProperties;

    @Autowired
    private EurekaInstanceConfigBean eurekaInstanceConfigBean;
    /**
     * last apps hash code is used to identify the {@link Applications} is changed or not
     */
    private String lastAppsHashCode;
    private String lastStoneAppsHashCode;

    private final ScheduledExecutorService  scheduler = Executors.newScheduledThreadPool(2,
        new ThreadFactoryBuilder()
            .setNameFormat("stone-DiscoveryClient-%d")
            .setDaemon(true)
            .build());



    @Override
    public void register() {
        setInstanceStatus(InstanceInfo.InstanceStatus.UP);
    }

    @Override
    public void unRegister() {
        setInstanceStatus(InstanceInfo.InstanceStatus.OUT_OF_SERVICE);
    }

    private void synEurekaClient() {
        registerEurekaEventListener(eurekaClient);
    }

    @Override
    public void synProviderMeta() {
        onCacheRefreshedEvent();
        synEurekaClient();
        int registryFetchIntervalSeconds = eurekaClient.getEurekaClientConfig().getRegistryFetchIntervalSeconds();
        scheduler.scheduleAtFixedRate(
            ()->onCacheRefreshedEvent(),3,
            registryFetchIntervalSeconds, TimeUnit.SECONDS);
    }


    private void registerEurekaEventListener(EurekaClient eurekaClient) {
        eurekaClient.registerEventListener(this::onEurekaEvent);
    }

    private void onEurekaEvent(EurekaEvent event) {
        if (event instanceof CacheRefreshedEvent) {
            onCacheRefreshedEvent();
        }
    }

    private void onCacheRefreshedEvent() {
        synchronized (this) { // Make sure thread-safe in async execution
            Applications applications = eurekaClient.getApplications();
            String appsHashCode = applications.getAppsHashCode();
            String stoneAppsHashCode = StoneConsumerMeta.getAppsHashCode();
            if (!Objects.equals(lastAppsHashCode, appsHashCode)||!Objects.equals(lastStoneAppsHashCode,stoneAppsHashCode)) { // Changed
                List<StoneURL> urlList= new ArrayList<StoneURL>();

                applications.getRegisteredApplications().stream().forEach(application -> {
                    application.getInstances().stream().forEach(instanceInfo -> {
                        Map<String, String> meta = instanceInfo.getMetadata();
                        StoneURL stoneURL = new StoneURL();
                        stoneURL.setHost(meta.get("stone.rpc.host"));
                        stoneURL.setPort(Integer.parseInt(meta.get("stone.rpc.port")));
                        stoneURL.setServerName(instanceInfo.getAppName());
                        Map<String, String> parameters = new HashMap<>();
                        parameters.put("appName", instanceInfo.getAppName());
                        parameters.put("appVersion", meta.get("appVersion"));
                        stoneURL.getParameters().putAll(parameters);
                        stoneURL.setServerName(instanceInfo.getAppName());
                        StoneConsumerVersion consumerVersion = new StoneConsumerVersion(meta.get("appVersion"));
                        stoneURL.setStoneConsumerVersion(consumerVersion);
                        urlList.add(stoneURL);
                    });
                });

                StoneConsumerMeta.resetStoneUrls(urlList);
                lastStoneAppsHashCode=stoneAppsHashCode;
                lastAppsHashCode = appsHashCode; // update current result
            }
        }
    }


    private void setInstanceStatus(InstanceInfo.InstanceStatus status) {
        if (applicationInfoManager != null) {
            this.applicationInfoManager.setInstanceStatus(status);
        }
    }
}
